package org.manon.json.asm;

import static org.manon.json.asm.ASMUtil.TYPE_INPUT_READER;
import static org.manon.json.asm.ASMUtil.TYPE_MAP;
import static org.manon.json.asm.ASMUtil.TYPE_OBJECT;
import static org.manon.json.asm.ASMUtil.TYPE_SETTER;
import static org.manon.json.asm.ASMUtil.TYPE_STRING;
import static org.manon.json.asm.ASMUtil.TYPE_VOID;
import static org.manon.json.asm.ASMUtil.TYPE_BOOLEAN;
import static org.manon.json.asm.ASMUtil.TYPE_STRING_BUILDER;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;

import org.manon.json.JSONException;
import org.manon.json.Setter;
import org.manon.json.analyse.ClassRegistry;
import org.manon.json.analyse.TreeNode;
import org.manon.json.io.Converter;
import org.manon.json.io.Deserializer;
import org.manon.json.io.InputReader;
import org.manon.json.meta.Entity;
import org.manon.json.meta.MetaProperty;
import org.manon.json.meta.MetaPropertyAssociation;
import org.manon.json.meta.MetaPropertyCollection;
import org.manon.json.meta.MetaPropertyDiscriminator;
import org.manon.json.meta.MetaPropertyGeneric;
import org.manon.json.meta.MetaPropertyMap;
import org.manon.json.meta.MetaPropertyType;
import org.manon.json.util.ClassUtils;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

/**
 * Generate deserialzer with ASM.
 * 
 * @author <a href="mailto:jacques.militello@gmail.com">Jacques Militello</a>
 */
public final class ASMGenerator4Deserializer extends ASMBaseGenerator {

	public static final Type TYPE_DESERIALIZER = Type.getType(Deserializer.class);
	
	private final ASMConverterHolder _converterHolder = new ASMConverterHolder();

	private final MethodVisitorTemplate<ASMContextDeSerializer> _templateConstructor = new GenerateConstructor();
	private final MethodVisitorTemplate<ASMContextDeSerializer> _templateFields = new GenerateClassFields();
	private final MethodVisitorTemplate<ASMContextDeSerializer> _templateSetter = new GenerateGetSetter();
	private final MethodVisitorTemplate<ASMContextDeSerializer> _templateNewInstanceReturnObject = new GenerateNewInstanceReturnObject();
	
	private final MethodVisitorTemplate<ASMContextDeSerializer> _templatePojoHasSubClass = new GeneratePojoHasSubClass();
	
	private final MethodVisitorTemplate<ASMContextDeSerializerInner> _innerTemplateConstructor = new GenerateInnerConstructor();
	private final MethodVisitorTemplate<ASMContextDeSerializerInner> _innerTemplateSetObject = new GenerateInnerSetObject();
	
	ASMGenerator4Deserializer(ClassRegistry classRegistry) {
		super(classRegistry);
	}
	
	void generateClassHeader(Entity entity, ClassWriter classWriter) {

		final String[] path = ClassUtils.path(entity.getEntityClass());
		final boolean hasChild = getRegistry().hasChild(entity);
		
		if (path.length > 1) {
			final Entity parentEntity = getRegistry().search(Arrays.copyOfRange(path, 0, path.length-1));
			classWriter.visit(ASMUtil.JAVA_VERSION,	Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER + ((hasChild) ? 0 : Opcodes.ACC_FINAL),
					entity.getInternalClassNameForDeserializer(),
					null,
					parentEntity.getInternalClassNameForDeserializer() ,
					null);	
		} else {
			classWriter.visit(ASMUtil.JAVA_VERSION, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER + ((hasChild) ? 0 : Opcodes.ACC_FINAL),
					entity.getInternalClassNameForDeserializer(),
					ASMUtil.getSignature(ASMDeserializer.class, entity.getEntityClass()),
					Type.getInternalName(ASMDeserializer.class)  ,
					null);
		}
		
	}
	
	void generateBody(Entity entity, ClassWriter classWriter, ASMClassFactory holder) {
		ASMContextDeSerializer context =  new ASMContextDeSerializer(classWriter,entity,holder);
		
		_templateConstructor.setContext(context);
		_templateFields.setContext(context);
		_templateSetter.setContext(context);
		_templateNewInstanceReturnObject.setContext(context);
		_templatePojoHasSubClass.setContext(context);
		
		try {
			_templateFields.create();
			_templateConstructor.create();
			_templateNewInstanceReturnObject.create();
			_templatePojoHasSubClass.create();
			generateNewInstance(context);
			_templateSetter.create();
			generateAccess(context);
		} catch (Exception exception) {
			throw new JSONException("Failed to build DeSerialiser for entity [" + entity + "]",exception);
		}
		
	}
	
	private void generateAccess(ASMContextDeSerializer context) {
		if (context.getAccessMethods().isEmpty()) {
			return;
		}
		for (ASMAppendToClass accessMethod : context.getAccessMethods()) {
			accessMethod.append(context.getClassWriter());
		}
	}

	private String getDeserializer(String name) {
		return new StringBuilder().append("DESERIALIZER_").append(name.toUpperCase()).toString();
	}
	
	private void generateClassFieldsAssociation(final ASMContextDeSerializer context,MethodVisitor mv) {
		
		Class<?> objectClass = null;
		for (MetaProperty property : context.getEntity().getProperties()) {
			
			switch (property.getType()) {
				case COLLECTION:
					objectClass = ((MetaPropertyCollection)property).getCollectionItemClass();
					break;
				case ASSOCIATION:	
					objectClass = property.getReturnType();
					break;
				case MAP :
					System.out.println("generateClassFieldsAssociation ***************************************************************************");
					
					objectClass = ((MetaPropertyMap)property).getValueItem();
					
					if (ASMUtil.isSingleValue(objectClass)) {
						objectClass = null;
						continue;
					}
					
					System.out.println("generateClassFieldsAssociation **** " + objectClass );
					break;
					
				default:
					objectClass = null;
					break;
			}

			if (objectClass != null && !context.containsDeserializer(objectClass)) {

				if (objectClass.isInterface()) {
					//System.out.println("DESER ....................... INTERFACE ...");
					continue;
				}
				
				final Entity entity = findEntityClass(objectClass);
				
				context.getClassWriter().visitField(ASMUtil.publicStaticFinal(),getDeserializer(objectClass.getSimpleName()),ASMUtil.getDescriptor(entity.getInternalClassNameForDeserializer()), null,null); 
			
				mv.visitTypeInsn(Opcodes.NEW, entity.getInternalClassNameForDeserializer());
				mv.visitInsn(Opcodes.DUP);
				mv.visitMethodInsn(Opcodes.INVOKESPECIAL, entity.getInternalClassNameForDeserializer(),"<init>","()V");
				mv.visitFieldInsn(Opcodes.PUTSTATIC, context.getClassName(), getDeserializer(objectClass.getSimpleName()), ASMUtil.getDescriptor(entity.getInternalClassNameForDeserializer()));
			
				final String accessName = "access$" +(context.sizeDeserializers()+1);
				final Class<?> objectClassToto = objectClass;
				
				context.addAccessMethod(new ASMAppendToClass() {
					public void append(ClassWriter classWriter) {
						MethodVisitor mvAccess = context.getClassWriter().visitMethod(Opcodes.ACC_STATIC, accessName , "()L"+entity.getInternalClassNameForDeserializer()+";", null, null);
						mvAccess.visitCode();
						mvAccess.visitFieldInsn(Opcodes.GETSTATIC, context.getClassName(),getDeserializer(objectClassToto.getSimpleName()), ASMUtil.getDescriptor(entity.getInternalClassNameForDeserializer()));
						mvAccess.visitInsn(Opcodes.ARETURN);
						mvAccess.visitMaxs(1,1);
						mvAccess.visitEnd();
					}
				});
			
				context.addDeserializer(objectClass);
			}
		}
		
	}
	
	private void generateNewInstance(ASMContextDeSerializer context) {
		// 2. generate return entity pojo
		final MethodVisitor mv2 = context.getClassWriter().visitMethod(Opcodes.ACC_PUBLIC,"newPojoInstance", Type.getMethodDescriptor(Type.getType(context.getEntity().getEntityClass())),null,null);
		mv2.visitCode();
		mv2.visitTypeInsn(Opcodes.NEW, Type.getInternalName(context.getEntity().getEntityClass()));
		mv2.visitInsn(Opcodes.DUP);
		mv2.visitMethodInsn(Opcodes.INVOKESPECIAL,Type.getInternalName(context.getEntity().getEntityClass()),"<init>","()V");
		mv2.visitInsn(Opcodes.ARETURN);
		mv2.visitMaxs(1, 1);
		mv2.visitEnd();
		
		// 2. generate return entity pojo
		final MethodVisitor methodVisitorDisc = context.getClassWriter().visitMethod(Opcodes.ACC_PUBLIC,"getSubDeserializer", Type.getMethodDescriptor(TYPE_DESERIALIZER,TYPE_STRING),null,null);
		methodVisitorDisc.visitCode();
		for (ASMAppendToMethod toAppend : context.getNewInstance()) {
			toAppend.append(methodVisitorDisc);
		}
		//methodVisitorDisc.visitMethodInsn(Opcodes.INVOKESPECIAL,Type.getInternalName(context.getEntity().getEntityClass()),"<init>","()V");
		
		methodVisitorDisc.visitTypeInsn(Opcodes.NEW, Type.getInternalName(IllegalArgumentException.class));
		methodVisitorDisc.visitInsn(Opcodes.DUP);
		methodVisitorDisc.visitTypeInsn(Opcodes.NEW, TYPE_STRING_BUILDER.getInternalName());
		methodVisitorDisc.visitInsn(Opcodes.DUP);
		methodVisitorDisc.visitLdcInsn("Invalid Discriminator [");
		methodVisitorDisc.visitMethodInsn(Opcodes.INVOKESPECIAL,TYPE_STRING_BUILDER.getInternalName(),"<init>","(Ljava/lang/String;)V");
		methodVisitorDisc.visitVarInsn(Opcodes.ALOAD, 1);
		methodVisitorDisc.visitMethodInsn(Opcodes.INVOKEVIRTUAL, TYPE_STRING_BUILDER.getInternalName(),"append", Type.getMethodDescriptor(TYPE_STRING_BUILDER,TYPE_STRING));
		methodVisitorDisc.visitLdcInsn("]");
		methodVisitorDisc.visitMethodInsn(Opcodes.INVOKEVIRTUAL, TYPE_STRING_BUILDER.getInternalName(),"append", Type.getMethodDescriptor(TYPE_STRING_BUILDER,TYPE_STRING));
		methodVisitorDisc.visitMethodInsn(Opcodes.INVOKEVIRTUAL, TYPE_STRING_BUILDER.getInternalName(),"toString", Type.getMethodDescriptor(TYPE_STRING));
		methodVisitorDisc.visitMethodInsn(Opcodes.INVOKESPECIAL,Type.getInternalName(IllegalArgumentException.class),"<init>","(Ljava/lang/String;)V");
		methodVisitorDisc.visitInsn(Opcodes.ATHROW);
		
		methodVisitorDisc.visitMaxs(1, 1);
		methodVisitorDisc.visitEnd();
	}


	private byte[] buildInnerClass(String className,Class<?> propertyClass,ASMContextDeSerializer context,Class<?> returnType,Class<?> returnTypeToConvert,String name) throws Exception {
		
		context.getClassWriter().visitInnerClass(className, null, null, Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL);
		
		final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
		classWriter.visit(ASMUtil.JAVA_VERSION,ASMUtil.publicStaticFinal(),className,
				ASMUtil.getSignature(TYPE_SETTER, Type.getType(context.getEntity().getEntityClass()), Type.getType(propertyClass)),
				TYPE_OBJECT.getInternalName(),
				new String[]{TYPE_SETTER.getInternalName()});
		
		final ASMContextDeSerializerInner innerContext = new ASMContextDeSerializerInner(classWriter,className,context);
		
		_innerTemplateConstructor.setContext(innerContext);
		
		// constructor
		_innerTemplateConstructor.create();
		
		// object set
		final MethodVisitor mvObject = classWriter.visitMethod(Opcodes.ACC_PUBLIC,"set",Type.getMethodDescriptor(TYPE_VOID, TYPE_OBJECT,TYPE_OBJECT),null,null);
		mvObject.visitCode();
		mvObject.visitVarInsn(Opcodes.ALOAD, 0);
		mvObject.visitVarInsn(Opcodes.ALOAD, 1);
		mvObject.visitTypeInsn(Opcodes.CHECKCAST,Type.getInternalName(context.getEntity().getEntityClass()));
		mvObject.visitVarInsn(Opcodes.ALOAD, 2);
		mvObject.visitTypeInsn(Opcodes.CHECKCAST,Type.getInternalName(propertyClass));
		mvObject.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className,"set", Type.getMethodDescriptor(TYPE_VOID, Type.getType(context.getEntity().getEntityClass()), Type.getType(propertyClass)));
		mvObject.visitInsn(Opcodes.RETURN);
		mvObject.visitMaxs(1, 1);
		mvObject.visitEnd();
		
		final MethodVisitor mvGeneric = classWriter.visitMethod(Opcodes.ACC_PUBLIC,"set",Type.getMethodDescriptor(TYPE_VOID,Type.getType(context.getEntity().getEntityClass()),Type.getType(propertyClass)),null,null);
		mvGeneric.visitCode();
		mvGeneric.visitVarInsn(Opcodes.ALOAD, 1);
		mvGeneric.visitVarInsn(Opcodes.ALOAD, 2);
		
		if (Enum.class.isAssignableFrom(returnType)) {
			ASMConverterHolder.convertEnum(mvGeneric, returnType);
		} else 	if (!String.class.equals(returnType)) {
			_converterHolder.getConverter(returnTypeToConvert).convert(mvGeneric);	
		}
		
		mvGeneric.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(context.getEntity().getEntityClass()),ClassUtils.getSetter(name), 
				Type.getMethodDescriptor(TYPE_VOID, Type.getType(returnType)));
		
		mvGeneric.visitInsn(Opcodes.RETURN);
		mvGeneric.visitMaxs(1, 1);
		mvGeneric.visitEnd();
		classWriter.visitEnd();
		return classWriter.toByteArray();
	}
	
	private byte[] buildInnerAssociationClass(String className,ASMContextDeSerializer context,MetaPropertyAssociation property2) throws Exception {
		
		context.getClassWriter().visitInnerClass(className, null, null, Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL);
		
		final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
		
		classWriter.visit(ASMUtil.JAVA_VERSION,ASMUtil.publicStaticFinal(),className,
				ASMUtil.getSignature(TYPE_SETTER, Type.getType(context.getEntity().getEntityClass()), TYPE_INPUT_READER),
				TYPE_OBJECT.getInternalName(),
				new String[]{TYPE_SETTER.getInternalName()});
		
		final ASMContextDeSerializerInner innerContext = new ASMContextDeSerializerInner(classWriter,className,context);
		
		_innerTemplateConstructor.setContext(innerContext);
		_innerTemplateSetObject.setContext(innerContext);
		
		// constructor
		_innerTemplateConstructor.create();
		// object set
		_innerTemplateSetObject.create();
		
		// set ( pojo, inputReader)
		final MethodVisitor mvGeneric = classWriter.visitMethod(Opcodes.ACC_PUBLIC,"set",Type.getMethodDescriptor(TYPE_VOID, Type.getType(context.getEntity().getEntityClass()),TYPE_INPUT_READER),null,null);
		mvGeneric.visitCode();
		mvGeneric.visitVarInsn(Opcodes.ALOAD, 1);
		mvGeneric.visitVarInsn(Opcodes.ALOAD, 2);
		
		Entity entity = findEntityClass(property2.getReturnType());
		
		mvGeneric.visitMethodInsn(Opcodes.INVOKESTATIC, context.getClassName(),"access$" + context.indexOfDeserializer(property2.getReturnType()),"()L"+entity.getInternalClassNameForDeserializer()+";");
		mvGeneric.visitMethodInsn(Opcodes.INVOKEVIRTUAL, TYPE_INPUT_READER.getInternalName(),"read", 
				Type.getMethodDescriptor(TYPE_OBJECT, Type.getType(Deserializer.class)));
		mvGeneric.visitTypeInsn(Opcodes.CHECKCAST,Type.getInternalName(property2.getReturnType()));
		mvGeneric.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(context.getEntity().getEntityClass()),ClassUtils.getSetter(property2.getName()), 
				Type.getMethodDescriptor(TYPE_VOID, Type.getType(property2.getReturnType())));
		
		mvGeneric.visitInsn(Opcodes.RETURN);
		mvGeneric.visitMaxs(1, 1);
		mvGeneric.visitEnd();
		classWriter.visitEnd();
		return classWriter.toByteArray();
	}
	
	private byte[] buildInnerCollectionClass(String className, ASMContextDeSerializer context,MetaPropertyCollection property) throws Exception {
	
		context.getClassWriter().visitInnerClass(className, null, null, Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL);
		
		final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
		
		classWriter.visit(ASMUtil.JAVA_VERSION,ASMUtil.publicStaticFinal(),className,
				ASMUtil.getSignature(Setter.class, context.getEntity().getEntityClass(), InputReader.class),
				TYPE_OBJECT.getInternalName(),
				new String[]{TYPE_SETTER.getInternalName()});
		
		final ASMContextDeSerializerInner innerContext = new ASMContextDeSerializerInner(classWriter,className,context);
		
		_innerTemplateConstructor.setContext(innerContext);
		_innerTemplateSetObject.setContext(innerContext);
		
		// constructor
		_innerTemplateConstructor.create();
		// object set
		_innerTemplateSetObject.create();
		
		
		// set ( pojo, inputReader)
		final MethodVisitor mvGeneric = classWriter.visitMethod(Opcodes.ACC_PUBLIC,"set", Type.getMethodDescriptor(TYPE_VOID, Type.getType(context.getEntity().getEntityClass()),TYPE_INPUT_READER),null,null);
		mvGeneric.visitCode();
		
		mvGeneric.visitTypeInsn(Opcodes.NEW, Type.getInternalName(property.getCollectionClass()));
		mvGeneric.visitInsn(Opcodes.DUP);
		mvGeneric.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(property.getCollectionClass()),"<init>","()V");
		mvGeneric.visitVarInsn(Opcodes.ASTORE, 3);
		mvGeneric.visitVarInsn(Opcodes.ALOAD, 1);
		mvGeneric.visitVarInsn(Opcodes.ALOAD, 3);
		mvGeneric.visitMethodInsn(Opcodes.INVOKEVIRTUAL,Type.getType(context.getEntity().getEntityClass()).getInternalName(),ClassUtils.getSetter(property.getName()),  Type.getMethodDescriptor(TYPE_VOID, Type.getType(property.getReturnType())));
		
		
		mvGeneric.visitVarInsn(Opcodes.ALOAD, 2);
		mvGeneric.visitVarInsn(Opcodes.ALOAD, 3);
		
		Entity entity = findEntityClass(property.getCollectionItemClass());
		
		mvGeneric.visitFieldInsn(Opcodes.GETSTATIC, context.getClassName(),getDeserializer(property.getCollectionItemClass().getSimpleName()), ASMUtil.getDescriptor(entity.getInternalClassNameForDeserializer()));
		mvGeneric.visitMethodInsn(Opcodes.INVOKEVIRTUAL, TYPE_INPUT_READER.getInternalName(),"readArray",  "(Ljava/util/Collection;Lorg/manon/json/io/Deserializer;)V");
		
		Label returnLabel = new Label();
		mvGeneric.visitJumpInsn(Opcodes.GOTO, returnLabel);
		
		mvGeneric.visitVarInsn(Opcodes.ASTORE, 4);
		mvGeneric.visitTypeInsn(Opcodes.NEW, Type.getInternalName(JSONException.class));
		mvGeneric.visitInsn(Opcodes.DUP);
		mvGeneric.visitLdcInsn("Failed to read child");
		mvGeneric.visitVarInsn(Opcodes.ALOAD, 4);
		mvGeneric.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(JSONException.class),"<init>","(Ljava/lang/String;Ljava/lang/Throwable;)V");
		mvGeneric.visitInsn(Opcodes.ATHROW);
		
		mvGeneric.visitLabel(returnLabel);
		mvGeneric.visitInsn(Opcodes.RETURN);
		mvGeneric.visitMaxs(1, 1);
		mvGeneric.visitEnd();
		classWriter.visitEnd();
		return classWriter.toByteArray();
	}

	public byte[] buildInnerMapClass(String className,ASMContextDeSerializer context, MetaPropertyMap property) throws Exception {

		context.getClassWriter().visitInnerClass(className, null, null, Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL);
		
		final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
		
		classWriter.visit(ASMUtil.JAVA_VERSION,ASMUtil.publicStaticFinal(),className,
				ASMUtil.getSignature(Setter.class, context.getEntity().getEntityClass(), InputReader.class),
				TYPE_OBJECT.getInternalName(),
				new String[]{TYPE_SETTER.getInternalName()});
		
		final ASMContextDeSerializerInner innerContext = new ASMContextDeSerializerInner(classWriter,className,context);
		
		_innerTemplateConstructor.setContext(innerContext);
		_innerTemplateSetObject.setContext(innerContext);
		
		// constructor
		_innerTemplateConstructor.create();
		// object set
		_innerTemplateSetObject.create();
		
		
		// set ( pojo, inputReader)
		final MethodVisitor mvGeneric = classWriter.visitMethod(Opcodes.ACC_PUBLIC,"set", Type.getMethodDescriptor(TYPE_VOID, Type.getType(context.getEntity().getEntityClass()),TYPE_INPUT_READER),null,null);
		mvGeneric.visitCode();
		
		// Map map = new HashMap();
		mvGeneric.visitTypeInsn(Opcodes.NEW, Type.getInternalName(property.getMapClass()));
		mvGeneric.visitInsn(Opcodes.DUP);
		mvGeneric.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(property.getMapClass()),"<init>","()V");
		mvGeneric.visitVarInsn(Opcodes.ASTORE, 3);
		
		// pojo.set<PropertyName>(map);
		mvGeneric.visitVarInsn(Opcodes.ALOAD, 1);
		mvGeneric.visitVarInsn(Opcodes.ALOAD, 3);
		mvGeneric.visitMethodInsn(Opcodes.INVOKEVIRTUAL,Type.getType(context.getEntity().getEntityClass()).getInternalName(),ClassUtils.getSetter(property.getName()),  Type.getMethodDescriptor(TYPE_VOID, Type.getType(property.getReturnType())));
		
		if (ASMUtil.isSingleValue(property.getKeyItem()) && ASMUtil.isSingleValue(property.getValueItem())) {
			mvGeneric.visitVarInsn(Opcodes.ALOAD, 2);
			mvGeneric.visitVarInsn(Opcodes.ALOAD, 3);
			
			mvGeneric.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(Converter.class),property.getKeyItem().getSimpleName().toUpperCase()+ "_CONVERTER", Type.getDescriptor(Converter.class));			
			mvGeneric.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(Converter.class),property.getValueItem().getSimpleName().toUpperCase()+ "_CONVERTER", Type.getDescriptor(Converter.class));			
			
			mvGeneric.visitMethodInsn(Opcodes.INVOKEVIRTUAL, TYPE_INPUT_READER.getInternalName(),"readMap",  "(Ljava/util/Map;Lorg/manon/json/io/Converter;Lorg/manon/json/io/Converter;)V");
		} else if (ASMUtil.isSingleValue(property.getKeyItem())) {
			mvGeneric.visitVarInsn(Opcodes.ALOAD, 2);
			mvGeneric.visitVarInsn(Opcodes.ALOAD, 3);
			mvGeneric.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(Converter.class),property.getKeyItem().getSimpleName().toUpperCase()+ "_CONVERTER", Type.getDescriptor(Converter.class));			
			Entity entity = findEntityClass(property.getValueItem());
			mvGeneric.visitFieldInsn(Opcodes.GETSTATIC, context.getClassName(),getDeserializer(property.getValueItem().getSimpleName()), ASMUtil.getDescriptor(entity.getInternalClassNameForDeserializer()));
			mvGeneric.visitMethodInsn(Opcodes.INVOKEVIRTUAL, TYPE_INPUT_READER.getInternalName(),"readMap",  "(Ljava/util/Map;Lorg/manon/json/io/Converter;Lorg/manon/json/io/Deserializer;)V");
		} else {
			throw new JSONException("Serialize Map with key is not a single value");
		}
		
//		mvGeneric.visitVarInsn(Opcodes.ALOAD, 2);
//		mvGeneric.visitVarInsn(Opcodes.ALOAD, 3);
//		
//		Entity entity = findEntityClass(property.getCollectionItemClass());
		
		
		
//		
//		mvGeneric.visitFieldInsn(Opcodes.GETSTATIC, context.getClassName(),getDeserializer(property.getCollectionItemClass().getSimpleName()), ASMUtil.getDescriptor(entity.getInternalClassNameForDeserializer()));
//		mvGeneric.visitMethodInsn(Opcodes.INVOKEVIRTUAL, TYPE_INPUT_READER.getInternalName(),"readArray",  "(Ljava/util/Collection;Lorg/manon/json/io/Deserializer;)V");
//		
//		Label returnLabel = new Label();
//		mvGeneric.visitJumpInsn(Opcodes.GOTO, returnLabel);
//		
//		mvGeneric.visitVarInsn(Opcodes.ASTORE, 4);
//		mvGeneric.visitTypeInsn(Opcodes.NEW, Type.getInternalName(JSONException.class));
//		mvGeneric.visitInsn(Opcodes.DUP);
//		mvGeneric.visitLdcInsn("Failed to read child");
//		mvGeneric.visitVarInsn(Opcodes.ALOAD, 4);
//		mvGeneric.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(JSONException.class),"<init>","(Ljava/lang/String;Ljava/lang/Throwable;)V");
//		mvGeneric.visitInsn(Opcodes.ATHROW);
		
	//	mvGeneric.visitLabel(returnLabel);
		mvGeneric.visitInsn(Opcodes.RETURN);
		mvGeneric.visitMaxs(1, 1);
		mvGeneric.visitEnd();
		classWriter.visitEnd();
		return classWriter.toByteArray();
	}

	// ************************************************************************
	// inner classes
	// ************************************************************************
	
	private final class GenerateConstructor extends MethodVisitorTemplate<ASMContextDeSerializer> {

		public GenerateConstructor() {
			super(Opcodes.ACC_PUBLIC,"<init>","()V",null,null);
		}
		
		/**
		 * @see org.manon.json.asm.MethodVisitorTemplate#doCreate(org.manon.json.asm.ASMContext, org.objectweb.asm.MethodVisitor)
		 */
		@Override
		protected void doCreate(ASMContextDeSerializer context, MethodVisitor methodVisitor) throws Exception {
			methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
			methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL,Type.getInternalName(ASMDeserializer.class),"<init>","()V");
			
			for (ASMAppendToMethod toAppend : context.getAppendToConstructor()) {
				toAppend.append(methodVisitor);
			}
		}
	}
	
	private final class GenerateClassFields extends MethodVisitorTemplate<ASMContextDeSerializer> {

		public GenerateClassFields() {
			super(Opcodes.ACC_STATIC , "<clinit>", "()V", null, null);
		}
		
		/**
		 * @see org.manon.json.asm.MethodVisitorTemplate#doCreate(org.manon.json.asm.ASMContext, org.objectweb.asm.MethodVisitor)
		 */
		@Override
		protected void doCreate(final ASMContextDeSerializer context, MethodVisitor methodVisitor) throws Exception {
			
			int innerClassCount = 1;
			
			// create association serializers.
			generateClassFieldsAssociation(context,methodVisitor);
			
			// create Map of Setter.
			context.getClassWriter().visitField(ASMUtil.protectedStaticFinal(), "SETTERS", TYPE_MAP.getDescriptor(), ASMUtil.getSignature(TYPE_MAP,TYPE_STRING,TYPE_SETTER), null);
			
			//create HashMap;
			methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getType(HashMap.class).getInternalName());
			methodVisitor.visitInsn(Opcodes.DUP);
			
			
			String[] entityPath = ClassUtils.path(context.getEntity().getEntityClass());
			
			if (entityPath.length > 1) {
				methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getRegistry().search(Arrays.copyOfRange(entityPath, 0, entityPath.length-1)).getInternalClassNameForDeserializer(), "SETTERS" , TYPE_MAP.getDescriptor());
				methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getType(HashMap.class).getInternalName(),"<init>","(Ljava/util/Map;)V");
			} else {
				methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getType(HashMap.class).getInternalName(),"<init>","()V");
			}
			
			methodVisitor.visitFieldInsn(Opcodes.PUTSTATIC, context.getClassName(), "SETTERS",TYPE_MAP.getDescriptor());
			
			String innerClassName, innerDeserializer;
			
			for (MetaProperty property : context.getEntity().getProperties()) {
				
				 if (property instanceof MetaPropertyGeneric && !((MetaPropertyGeneric)property).isInstance()){
						//System.out.println("************************ DESER -> GENERIC ...");
						 continue;
					}
				 
				
				innerClassName = new StringBuilder(64).append(context.getClassName()).append("$").append(innerClassCount).toString();
				innerDeserializer = new StringBuilder(64).append(context.getEntity().getClassNameForDeserializer()).append("$").append(innerClassCount).toString();
				
				switch (property.getType()) {
					case COLLECTION:
						if (((MetaPropertyCollection)property).getCollectionItemClass().isInterface()) {
							System.out.println("************ DESER collection interface ....");
							continue;
						}
						
						context.getHolder().registerInnerClasses(innerDeserializer, buildInnerCollectionClass(innerClassName,context,(MetaPropertyCollection)property));
						break;
					case ASSOCIATION:
						context.getHolder().registerInnerClasses(innerDeserializer, buildInnerAssociationClass(innerClassName,context,(MetaPropertyAssociation)property));
						break;
					case GENERIC:
						context.getHolder().registerInnerClasses(innerDeserializer, buildInnerClass(innerClassName,String.class,context,Object.class,property.getReturnType(),property.getName()));
						break;
					case MAP:
						context.getHolder().registerInnerClasses(innerDeserializer, buildInnerMapClass(innerClassName,context,(MetaPropertyMap)property));
						break;
					default :
						context.getHolder().registerInnerClasses(innerDeserializer, buildInnerClass(innerClassName,String.class,context,property.getReturnType(),property.getReturnType(),property.getName()));	
				}
					
				context.getClassWriter().visitField(ASMUtil.publicStaticFinal(), "SETTER_" + property.getName().toUpperCase() , 
						TYPE_SETTER.getDescriptor(), ASMUtil.getSignature(TYPE_SETTER, Type.getType(context.getEntity().getEntityClass()), TYPE_STRING),null);
				
				methodVisitor.visitTypeInsn(Opcodes.NEW, innerClassName);
				methodVisitor.visitInsn(Opcodes.DUP);
				methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, innerClassName,"<init>","()V");
				methodVisitor.visitFieldInsn(Opcodes.PUTSTATIC, context.getClassName(), "SETTER_" + property.getName().toUpperCase(),Type.getDescriptor(Setter.class));
				innerClassCount++;
			}
			
			for (MetaProperty property : context.getEntity().getProperties()) {
				
				 if (property instanceof MetaPropertyGeneric && !((MetaPropertyGeneric)property).isInstance()){
						//System.out.println("************************ DESER 2 -> GENERIC ...");
						 continue;
				 }

				 if ((MetaPropertyType.COLLECTION == property.getType()) && ((MetaPropertyCollection)property).getCollectionItemClass().isInterface()) {
						System.out.println("************ DESER collection interface ....");
						continue;
					}
				
				methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, context.getClassName(), "SETTERS" , TYPE_MAP.getDescriptor());
				methodVisitor.visitLdcInsn(property.getLabel());
				methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, context.getClassName(), "SETTER_" + property.getName().toUpperCase() , TYPE_SETTER.getDescriptor());
				methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, TYPE_MAP.getInternalName(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
				methodVisitor.visitInsn(Opcodes.POP);
			}
			
			
			if (getRegistry().hasChild(context.getEntity())) {
				if (context.getEntity().getProperties().size() > 0) {
					String[] path = ClassUtils.path(context.getEntity().getEntityClass());
					TreeNode<Entity, String> node = getRegistry().searchPath(ClassUtils.path(context.getEntity().getEntityClass()));
					if (!(context.getEntity().getProperties().get(0) instanceof MetaPropertyDiscriminator)) {
						for (int i = path.length-1 ; i > 0 ; i--) {
							Entity itemEntity = getRegistry().search(Arrays.copyOfRange(path, 0, i));
							if (itemEntity.getProperties().size() > 0 && itemEntity.getProperties().get(0) instanceof MetaPropertyDiscriminator) {
								add(node.iterator(),context,methodVisitor);
								break;
							}
						}
					} else {
						add(node.iterator(),context,methodVisitor);
					}
				}
			}
			
		}
	}
	
	private static void add(Iterator<TreeNode<Entity, String>> iterator,final ASMContextDeSerializer context, MethodVisitor methodVisitor) {
		
		TreeNode<Entity, String> child;
		while (iterator.hasNext()) {
		
			child = iterator.next();
			
			final String fieldName = "DESER_"+child.getId().toUpperCase();
			final Entity childEntity = child.getObject();
			
			if (!child.isLeaf()) {
				add(child.iterator(),context,methodVisitor);
			}

			context.getClassWriter().visitField(Opcodes.ACC_PROTECTED + Opcodes.ACC_FINAL,fieldName,ASMUtil.getDescriptor(childEntity.getInternalClassNameForDeserializer()),
					ASMUtil.getDescriptor(childEntity.getInternalClassNameForDeserializer()),null);
			
			context.getAppendToConstructor().add(new ASMAppendToMethod() {
				public void append(MethodVisitor methodVisitor) {
					methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
					methodVisitor.visitTypeInsn(Opcodes.NEW, childEntity.getInternalClassNameForDeserializer());
					methodVisitor.visitInsn(Opcodes.DUP);
					methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, childEntity.getInternalClassNameForDeserializer(),"<init>","()V");
					methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, context.getClassName(), fieldName,ASMUtil.getDescriptorForDeSerializer(childEntity));
				}
			});
			
			context.appendNewInstance(new ASMAppendToMethod() {
				public void append(MethodVisitor methodVisitor) {
					methodVisitor.visitLdcInsn(((MetaPropertyDiscriminator)childEntity.getProperties().get(0)).getValue());
					Label returnLabel = new Label();
					methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
					methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String","equals", "(Ljava/lang/Object;)Z");
					methodVisitor.visitJumpInsn(Opcodes.IFEQ, returnLabel);
					methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
					methodVisitor.visitFieldInsn(Opcodes.GETFIELD, context.getClassName(),fieldName, ASMUtil.getDescriptor(childEntity.getInternalClassNameForDeserializer()));
					methodVisitor.visitInsn(Opcodes.ARETURN);
					methodVisitor.visitLabel(returnLabel);
				}
			});
			
		}
	}

	private static final class GenerateGetSetter extends MethodVisitorTemplate<ASMContextDeSerializer> {

		public GenerateGetSetter() {
			super(Opcodes.ACC_PUBLIC,"getSetter",Type.getMethodDescriptor(TYPE_SETTER, TYPE_STRING),null,null,Opcodes.ARETURN);
		}
		/**
		 * @see org.manon.json.asm.MethodVisitorTemplate#doCreate(org.manon.json.asm.ASMContext, org.objectweb.asm.MethodVisitor)
		 */
		@Override
		protected void doCreate(ASMContextDeSerializer context, MethodVisitor methodVisitor) throws Exception {
			methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, context.getClassName(), "SETTERS" , TYPE_MAP.getDescriptor());
			methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
			methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, TYPE_MAP.getInternalName(), "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
			methodVisitor.visitTypeInsn(Opcodes.CHECKCAST,TYPE_SETTER.getInternalName());
		}
	}
	
	private final class GeneratePojoHasSubClass extends MethodVisitorTemplate<ASMContextDeSerializer> {
		public GeneratePojoHasSubClass() {
			super(Opcodes.ACC_PUBLIC,"pojoHasSubClass",Type.getMethodDescriptor(TYPE_BOOLEAN),null,null,Opcodes.IRETURN);
		}
		/**
		 * @see org.manon.json.asm.MethodVisitorTemplate#doCreate(org.manon.json.asm.ASMContext, org.objectweb.asm.MethodVisitor)
		 */
		@Override
		protected void doCreate(ASMContextDeSerializer context, MethodVisitor methodVisitor) throws Exception {
			final boolean hasChild = getRegistry().hasChild(context.getEntity());
			if (hasChild) {
				methodVisitor.visitInsn(Opcodes.ICONST_1);
			} else {
				methodVisitor.visitInsn(Opcodes.ICONST_0);
			}
		}
	}
	
	/**
	 * generate return Object;
	 */
	private static final class GenerateNewInstanceReturnObject extends MethodVisitorTemplate<ASMContextDeSerializer> {

		public GenerateNewInstanceReturnObject() {
			super(Opcodes.ACC_PUBLIC,"newPojoInstance",	Type.getMethodDescriptor(TYPE_OBJECT),null,null,Opcodes.ARETURN);
		}
		/**
		 * @see org.manon.json.asm.MethodVisitorTemplate#doCreate(org.manon.json.asm.ASMContext, org.objectweb.asm.MethodVisitor)
		 */
		@Override
		protected void doCreate(ASMContextDeSerializer context, MethodVisitor methodVisitor) throws Exception {
			methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
			methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, context.getClassName(),"newPojoInstance", "()"+Type.getDescriptor(context.getEntity().getEntityClass()));
		}
	}
	
	// ************************************************************************
	// "inner" inner classes
	// ************************************************************************
	private static final class GenerateInnerConstructor extends MethodVisitorTemplate<ASMContextDeSerializerInner> {
		public GenerateInnerConstructor() {
			super(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
		}
		/**
		 * @see org.manon.json.asm.MethodVisitorTemplate#doCreate(org.manon.json.asm.ASMContext,org.objectweb.asm.MethodVisitor)
		 */
		@Override
		protected void doCreate(ASMContextDeSerializerInner context, MethodVisitor methodVisitor) throws Exception {
			methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
			methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL,TYPE_OBJECT.getInternalName(),"<init>","()V");
		}
	}
	
	private final class GenerateInnerSetObject extends MethodVisitorTemplate<ASMContextDeSerializerInner> {
		public GenerateInnerSetObject() {
			super(Opcodes.ACC_PUBLIC,"set", Type.getMethodDescriptor(TYPE_VOID, TYPE_OBJECT,TYPE_OBJECT),null,null);
		}
		/**
		 * @see org.manon.json.asm.MethodVisitorTemplate#doCreate(org.manon.json.asm.ASMContext,org.objectweb.asm.MethodVisitor)
		 */
		@Override
		protected void doCreate(ASMContextDeSerializerInner context, MethodVisitor methodVisitor) throws Exception {
			methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
			methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
			methodVisitor.visitTypeInsn(Opcodes.CHECKCAST,Type.getInternalName(context.getContext().getEntity().getEntityClass()));
			methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
			methodVisitor.visitTypeInsn(Opcodes.CHECKCAST,TYPE_INPUT_READER.getInternalName());
			methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, context.getClassName(),"set", Type.getMethodDescriptor(TYPE_VOID,Type.getType(context.getContext().getEntity().getEntityClass()), TYPE_INPUT_READER));
		}
	}
	
}